
/*
 yvmobile v0.4
 @author: Argenis Leon
 
 This library let you build off application in a easy way using Internet Windows Mobile.
 IMPORTANT: Opera Mobile 9.5 in his final release will suport Gears
 and Google Android already support it.
 (c)2008 Yaao, Argenis Leon (argenisleon@gmail.com) and Yosip Curiel (snake77@gmail.com) from vortex.offline library.
 
 The Sync Strategy implemented here is take it from dojo
 http://docs.google.com/View?docid=dhkhksk4_8gdp9gr
 *1 Sync downloads and caches our web user-interface AKA Slurp
 
 *2 Second, syncing happens automatically, without the user having to initiate it.
 Dojo Sync automatically kicks off syncing when the web application first loads and
 we are on the network, and also if we are offline and we detect that network has
 come back online. Especifically We upload any 'action' that happend while offline
 
 *3 Finally, We download the data from the server to make available offline
 
 
 CREDIT:
 - This library is based in the Brian Dunnington vortex.offline library
 http://geekycodestuff.blogspot.com/2007/09/vortexoffline-demo-app.html.
 - Very thanks to the Google Gears and Dojo Team by this great article
 http://docs.google.com/View?docid=dhkhksk4_8gdp9gr.
 - The yvmobile.utils.json function was taken from the following json proposal
 (http://www50.brinkster.com/zlib13/demos/json/json-proposal-demo.htm)
 - The yvmobile.utils.randomUUID functionwas taken from http://www.broofa.com/blog/?p=151
 - Thanks to the Opera Team by  the great article 'The seven rules of unobtrusive JavaScript'
 http://dev.opera.com/articles/view/the-seven-rules-of-unobtrusive-javascrip/
 
 */
// create our own namespace so we can play nice with other libraries
if (!yvmobile) {
    var yvmobile = {};
}

/**
 * @function yvmobile.offline
 * @description first things first. you must call the init() method first, passing at least the application name.
 * you can also optionally pass in any configuration values (see below) as well as a list of resources
 * (urls) to cache (these will be *in addition* to any auto-discovered files unless you set the autoDiscovery
 * configuration value to false, in which case *only* these urls will be cached)
 * configuration values:
 **/
yvmobile.offline = function(){
    var applicationName = "";
    var applicationUrl = "";
    var storeName = "";
    var error = "";
    var initialized = false;
    var offlineSupport = false;
    var offline = null;
    var localserver;
    var store;
    var filesToCapture;
    var isReplaying = false;
    
    // if true, all cached resources will be updated automatically when the application is online.
    // if false, the resources are never updated after being initally cached.
    // defaults to true.
    var autoRefresh = true;
    
    var autoSync = true;
    
    // if true, all supported resource types (css, images, etc) will automatically be cached for offline use.
    // if false, you must specify which resources should be cached.
    // defaults to true.
    var autoDiscovery = true;
    
    // The amount of time (in seconds) to poll the network to see if the application is online or offline.
    // defaults to 15 seconds. 
    var networkPollInterval = 30;
    
    // The url used to check network status. since the application uses an xhr request to determine connectivity,
    // you should use a relative url or url that will not be restricted by the cross-domain policy.
    // defaults to 'network_check.txt'
    var networkCheckUrl = location.href + "network_check.txt";
    
    //The amount of time (in seconds) to wait for the networkCheckUrl before giving up and assuming the application is offline.
    //defaults to 5 seconds.
    var networkTimeout = 15;
    
    var dbNamespace = "__yvmobile__offline__";
    
    // At inicialization we considere that the Last Sync happens so much time ago
    var lastSyncTime = new Date().getTime()-10000000000;

    // Max Time Between Sync in Seconds
    var timeBetweenSync = 60000;
    
    // private functions
    var addUrl = function(url){
    
        ;;;yvmobile.logger.log("addUrl:" + url);
        if (yvmobile.utils.sameDomain(url, window.location.href)) 
            filesToCapture[filesToCapture.length] = url;
    };
    
    var captureCallback = function(url, success, captureId){
        // If the file can not be downloaded
        if (success == false) {
            document.getElementById("sync-indicator").innerHTML = "<font color:#FF0000>SyncError:</font color>File " + url + " can not be downloaded";
        }
        ;;;yvmobile.logger.log("File " + url + " stored in Googgle Gears Cache. Success? " + success);
        
        // do any file-specific stuff here
        // also call the public event to let the application hook in here if desired
        yvmobile.offline.onFileCaptured(url, success, captureId);
    };
    
    var checkNetworkAvailability = function(){
        ;;;yvmobile.logger.log("checkNetworkAvailability");
        yvmobile.ajax.setRequestTimeout(networkTimeout);
        
        
        // HTTP Get are cacheable so we generate a random number every time we make a GET
        // in this we can be sure that the conection is really alive  
        yvmobile.ajax.request(networkCheckUrl + "?x=" + Math.random(), function(responseText, success){
            var previouslyOffline = offline;
            if (success == true) {
                // We are online
                offline = false;
            }
            else {
                // We are offline
                offline = true;
                
            }
            /* uncomment this if you want to test on your localhost, where it is hard to go 'offline'
             //debug
             var prefix = "http://localhost";
             var isLocal = (applicationUrl.substr(0,prefix.length)==prefix);
             if(!offline && isLocal && xhr.responseText != "1")
             offline = true;
             */
            if (previouslyOffline != offline || !initialized) {
                // handle any status changes
                networkStatusChange(offline);
                
                // if we are just initializing, fire the public event
                
                if (!initialized) {
                    initialized = true;
                    yvmobile.offline.onInitialized();
                }
                
                // also call the public event to let the application hook in here if desired
                yvmobile.offline.onNetworkStatusChange(offline);
                
                // Update the widget
                if (offline) {
                    document.getElementById("network-indicator").innerHTML = "<div class='offline'>offline</div>";
                    
                    document.body.className = "offline";
                    //log("Is Offline:" + offline);
                }
                else {
                    document.getElementById("network-indicator").innerHTML = "<div class='online'>online</div>";
                    document.body.className = "online";
                }
            }
            
            // set up to poll again
            window.setTimeout(checkNetworkAvailability, networkPollInterval * 1000);
        }, "GET", null);
        
    };
    
    var networkStatusChange = function(offline){
        ;;;yvmobile.logger.log("networkStatusChange");
        if (!offline && (autoRefresh || !initialized)) {
            // we dont want to remove the store and re-create it (although that would be easier) 
            // because we might lose the connection again before all of the files are refreshed. 
            // instead, we do each file individually
            
            //Update Widget to show the file capture
            //alert(document.getElementById("network-indicator").innerHTML);
            document.getElementById("sync-indicator").innerHTML = "<font color:#FF0000>Capturing Files to Download...</font color>";
            
            if (filesToCapture && filesToCapture.length) {
                for (var f = 0; f < filesToCapture.length; f++) {
                    var fileName = filesToCapture[f];
                    store.remove(fileName);
                    store.capture(fileName, captureCallback); // See *1 Caching Files
                }
                ;;;yvmobile.logger.info("Start Google Gears store file capture!!!");
            }
        }
        // If 'autoSync' is true and we como from 'offline' we try to update the data form the server
        // We are goint to sync if the connection status have change in less than x minutes
        if (!offline && autoSync) {//See *2
            diff = new Date().getTime() - lastSyncTime;
            //alert("timeBetweenSync: " + timeBetweenSync + "Diff: " + diff / 1000);
            if (timeBetweenSync < (diff / 1000)) {
                yvmobile.offline.sync();
                lastSyncTime = new Date().getTime();
            }
            else {
                document.getElementById("sync-indicator").innerHTML = "<font color:#FF0000>Short time has passed since last Sync. Sync Cancelled!!</font color>";             
            }
        }
    };
    
    // This method is executed by the sync() method
    var replayActions = function(){
        if (!isReplaying) {
            isReplaying = true;
            var actions = yvmobile.storage.getAll(dbNamespace);
            // replay each action
            for (var key in actions) {
                var action = actions[key];
                var success = yvmobile.offline.onReplayAction(action);
                if (success) {
                    // remove action from list
                    yvmobile.storage.remove(key, dbNamespace);
                }
                else {
                    // what if it fails?
                }
            }
            isReplaying = false;
        }
    };
    
    return {
        // public events that can be overridden per application
        // the following events can be overridden to perform any custom actions that your application may require:
        
        //Called when the offline features have fully been initialized (only if offline support is available)
        onInitialized: function(){
        },
        
        //Called once during initialization if offline support is not available (Google Gears not installed or not allowed to run)
        onNoOfflineSupport: function(){
        },
        
        //Called whenever the network status changes from online to offline or vice-versa
        onNetworkStatusChange: function(isNowOffline){
        },
        
        /**
         * @function
         * @description Called when a resource is captured
         * @param {Object} url
         * @param {Object} success
         * @param {Object} captureId
         */
        onFileCaptured: function(url, success, captureId){
        },
        
        // When the sync method is called, this event fires before any updates are performed
        onBeforeSync: function(){
        },
        
        onSync: function(){
        },
        
        // Called once the sync process is completed
        onSyncComplete: function(){
        },
        
        // During a sync operation, this is called for each action being replayed. this method should return true if the replay is successful, false otherwise
        onReplayAction: function(action){
            return true;
        },
        
        // public methods
        init: function(appName, config, files, debug){
            if (debug == null) {
                debug = false;
            }
            if (yvmobile.logger.init(debug) == false) {
                // Logger console canot be created
                alert("Logger can not be initialized");
                //return true;
            }
            ;;;yvmobile.logger.log("yvmobile.offline.init");
            
            // get basic application information
            applicationName = appName;
            applicationUrl = window.location.href;
            storeName = yvmobile.utils.replaceNonAlphaNumericCharacters(applicationUrl);
            dbNamespace += storeName;
            
            // handle overriding any configuration details
            if (config) {
                if (config.autoRefresh) 
                    autoRefresh = config.autoRefresh;
                if (config.autoSync) 
                    autoSync = config.autoSync;
                if (config.autoDiscovery) 
                    autoDiscovery = config.autoDiscovery;
                if (config.networkPollInterval) 
                    networkPollInterval = config.networkPollInterval;
                if (config.networkCheckUrl) 
                    networkCheckUrl = config.networkCheckUrl;
                if (config.networkTimeout) 
                    networkTimeout = config.networkTimeout;
                if (config.timeBetweenSync) 
                    timeBetweenSync = config.timeBetweenSync;
            
			}
            
            // initialize offline support
            ;;;yvmobile.logger.log("Gears Support?");
            if (window.google && google.gears) {
                try {
                    // if the user denies google gears the rights to run, this will fail
                    localserver = google.gears.factory.create('beta.localserver', '1.0');
                    
                    store = localserver.createStore(storeName);
                    
                    offlineSupport = true;
                    ;;;yvmobile.logger.log(offlineSupport);
                } 
                catch (err) {
                    txt = "There was an error on this page.\n\n";
                    txt += "Error description: " + err.description + "\n\n";
                    ;;;yvmobile.logger.error(txt);
                }
            }
            
            if (!offlineSupport) {
                ;;;yvmobile.logger.warn(offlineSupport);
                yvmobile.offline.onNoOfflineSupport();
                ;;;yvmobile.logger.info("Download Google Gears: <a style='padding-left:3px;border-bottom:solid #D7D7D7 1px; background-color:#FFFFFF; color:#000000' href='http://gears.google.com/'>Google Gears</a>");
                // Update Widget to Unsupported
                document.getElementById("network-indicator").innerHTML = "<div class='unsupported'>no offline support</div>";
                document.body.className = "unsupported";
                return true;
            }
            ;;;yvmobile.logger.log("Start Slurp!!!");
            
            // determine files needed to run offline
            filesToCapture = [];
            
            if (autoDiscovery) {
                ;;;yvmobile.logger.log("autoDiscovery:" + autoDiscovery);
                
                // find files and add them to the list
                var autoFiles = this.discoverFiles();
                ;;;yvmobile.logger.log("Files discovered in the Slurp: " + autoFiles);
                
                // add to list
                if (autoFiles && autoFiles.length && autoFiles.length > 0) {
                    for (var f = 0; f < autoFiles.length; f++) {
                        addUrl(autoFiles[f]);
                    }
                }
            }
            ;;;yvmobile.logger.log("Start Slurp for specific files");
            
            // add any additional files specified
            
            if (files && files.length && files.length > 0 && null != files) {
                for (var f = 0; f < files.length; f++) {
                    addUrl(files[f]);
                }
            }
            // do an initial network check and then start polling at regular intervals
            // so we know when we go on/offline
            ;;;yvmobile.logger.log("Verifyng network Availabitlity");
            checkNetworkAvailability();
            return true;
            
        },
        /**
         * @function discoverFiles()
         * @description Scans the application and returns a list of referenced resources (images, css, etc).
         * if autoDiscover is true, this is the list of files that will be cached for the application.
         * if autoDiscover is false, you can use this method to get a list of files to add/remove from to create your manual list
         * note that the following ARE NOT automatically discovered:
         *  - any referenced stylesheets (including @import)
         *  - any images referenced in the linked stylesheets
         *  - dynamically generated scripts or content
         *  - resources referenced by <object> or <embed> tags
         *  - ActiveX controls or Java Applets
         * @returns
         * a list of resources required by this page, including:
         *  - the page calling this script
         *  - any referenced javascript files
         *  - any inline images
         **/
        discoverFiles: function(){
            ;;;yvmobile.logger.log("discoverFiles");
            var elements;
            var e;
            var i;
            var files = [];
            
            // this page
            files.push(window.location.href);
            
            // referenced scripts
            // Important: If you use cakePHP it automaticaly add the javascript
            // to the file. So its not necesary to call the js external file */
            
            elements = document.getElementsByTagName("script");
            ;;;yvmobile.logger.log("Getting script tags...");
            
            
            for (i = 0; i < elements.length - 1; i++) {
                e = elements[i];
                //yvmobile.logger.log("element No "+ i+":" +e);
                // Internet Explorer Mobile may thrown an exception if
                // the attribute in getAttribute do not exist.
                // 
                try {
                    ;;;yvmobile.logger.log("Attribute: " + e.getAttribute('src'));
                    eSrc = e.getAttribute('src');
                    // If we can get the attribute verify if is not null or blank
                    
                    if (eSrc != null && eSrc != "") {
                        ;;;yvmobile.logger.log("Attribute: " + eSrc);
                        files.push(eSrc);
                        ;;;yvmobile.logger.log("script: " + e.getAttribute('src'));
                    }
                } 
                catch (err) {
                    ;;;yvmobile.logger.log("NOT exist attribute *src* in tag *script* ");
                }
            } // referenced inline images
            ;;;yvmobile.logger.log("Getting img tags...");
            elements = document.getElementsByTagName("img");
            
            for (i = 0; i < elements.length - 1; i++) {
                e = elements[i];
                //yvmobile.logger.log("element No "+ i+":" +e);
                // Internet Explorer Mobile may thrown an exception if
                // the attribute in getAttribute do not exist.
                // 
                try {
                    ;;;yvmobile.logger.log("Attribute:" + e.getAttribute('src'));
                    eScr = e.getAttribute('src');
                    
                    // If we can get the attribute verify if is not null or blank
                    if (eSrc != null && eSrc != "") {
                        ;;;yvmobile.logger.log("Attribute:" + eSrc);
                        files.push(eSrc);
                        ;;;yvmobile.logger.log("script:" + e.getAttribute('src'));
                    }
                } 
                catch (err) {
                
                    ;;;yvmobile.logger.log("NOT exist attribute *src* in tag *img*");
                }
            } // referenced stylesheets
            ;;;yvmobile.logger.log("Getting link tags AKA CSS...");
            elements = document.getElementsByTagName("link");
            for (i = 0; i < elements.length; i++) {
                e = elements[i];
                try {
                    eRel = e.getAttribute("rel");
                    if (eRel && eRel.toLowerCase() == "stylesheet") {
                        eHref = e.getAttribute("href");
                        if (eHref != null && eHref != '') {
                            ;;
                            ;
                            yvmobile.logger.log("link:" + eHref);
                            files.push(eHref);
                        }
                    }
                } 
                catch (err) {
                
                    ;;;yvmobile.logger.log("NOT exist attribute *href* in tag *link*");
                }
            }
            ;;;yvmobile.logger.log("Slurp Finished!!!");
            return files;
        },
        
        // isOnline() - return true if the application is currently online
        
        isOnline: function(){
            return !offline;
        },
        //       isOffline() - returns true if the application is currently offline
        
        isOffline: function(){
            return offline;
        },
        //     hasOfflineSupport() - returns true if the offline components are available (Google Gears installed and allowed to run). return false otherwise.
        
        hasOfflineSupport: function(){
            return offlineSupport;
        },
        /**sync() - replays any recorded actions to keep the server in sync.
         if autoSync is true, this method is automatically called when the application goes back online(It's calles too when the aplication init via onNetworkStatusChange).
         if autoSync is false, you must manually call this method to replay any recorded actions.
         recordAction(action) - records an action taken by the user or application so that it can be played back later (used to keep the server in sync)
         **/
        sync: function(){
        
            // fire public event
            yvmobile.offline.onBeforeSync();
            
            // replay any recorded actions
            ;;;yvmobile.logger.info("replayActions");
            document.getElementById("sync-indicator").innerHTML = "Uploading new data...";
            replayActions(); // See *2 Upload local actions
            ;;;yvmobile.logger.info("onSync");
            
            document.getElementById("sync-indicator").innerHTML = "Downloading new data...";
            // Call the callback function to download the data from the server
            yvmobile.offline.onSync(); //See *3 Download new data from the server
            // fire the public event
            
            ;;;yvmobile.logger.info("Sync Successfull");
            document.getElementById("sync-indicator").innerHTML = "Sync Successfull";
            yvmobile.offline.onSyncComplete();
        },
        
        recordAction: function(action){
        
            ;;;yvmobile.logger.log("recordAction");
            var key = new Date().toString();
            yvmobile.storage.put(key, action, dbNamespace);
        }
    }
}();

/**
 * @function yvmobile.storage
 * @description object is a singleton that is automatically initialized upon access, so
 * you do not have to initialize it and can access it at any time by using yvmobile.storage.method()
 * all items are stored in a common database and table. if you want to segregate your data, use
 * the 'namespace' parameter to keep different types of data or different parts of your application
 * stored individually
 * if Google Gears is not installed or not allowed to run, the storage methods will not do anything.
 * you can use yvmobile.storage.isAvailable() to check if storage capabilites are available.
 * All data is stored as a JSON representation of the object. if you pass in objects with circular
 * references, it will blow up.
 **/
yvmobile.storage = function(){
    var isAvailable = false;
    var dbName = "__yvmobile";
    var tableName = "__yvmobile__storage";
    var db = null;
    
    // initialize offline support
    if (window.google && google.gears) {
        try {
            db = google.gears.factory.create('beta.database', '1.0');
            db.open(dbName);
            
            db.execute("CREATE TABLE IF NOT EXISTS " + tableName + "( " +
            " namespace TEXT, " +
            " key TEXT, " +
            " value TEXT " +
            ")");
            db.execute("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index" +
            " ON " +
            tableName +
            " (namespace, key)");
            
            isAvailable = true;
        } 
        catch (err) {
        }
    }
    
    
    // Public functions
    /**
     * @function clear
     * @description removes all items currently saved in the given namespace
     * @param {Object} namespace
     */
    function clear(namespace){
        if (isAvailable) {
            db.execute("DELETE FROM " + tableName +
            " WHERE namespace = ?", [namespace]);
            return true;
        }
        else 
            return false;
    }
    /**
     * @function
     * @description retrives the object with the given key and namespace
     * @param {Object} key
     * @param {Object} namespace
     */
    function get(key, namespace){
        if (isAvailable) {
            var val = null;
            
            var rs = db.execute("SELECT * FROM " + tableName +
            " WHERE namespace = ? AND " +
            " key = ?", [namespace, key]);
            if (rs.isValidRow()) {
                val = rs.field(2);
                return yvmobile.utils.json.parse(val);
            }
            else {
                return null;
            }
        }
        else {
            return null;
        }
    }
    
    /**
     * @function getAll
     * @description retrieves a recordset containing all records in the given namespace
     * @param {Object} namespace
     */
    function getAll(namespace){
        if (isAvailable) {
            var list = [];
            var rs = db.execute("SELECT * FROM " + tableName +
            " WHERE namespace = ?", [namespace]);
            while (rs.isValidRow()) {
                list[rs.field(1)] = yvmobile.utils.json.parse(rs.field(2));
                rs.next();
            }
            rs.close();
            return list;
        }
        else 
            return null;
    }
    /**
     * @function isAvailable
     * @description verify if offline storage is available
     * @returns true if the storage provider is available (Google Gears is installed and allowed to run), otherwise returns false
     */
    function isAvailable(){
        return isAvailable;
    }
    
    /**
     * @function put
     * @description save the val object using the specified key and namespace.
     * if an item already exists with the given key and namespace, it will be verwritten.
     * @param {Object} key
     * @param {Object} val
     * @param {Object} namespace
     */
    function put(key, val, namespace){
        if (isAvailable) {
            var value = yvmobile.utils.json.stringify(val);
            
            yvmobile.storage.remove(key, namespace);
            db.execute("INSERT INTO " + tableName +
            " VALUES (?, ?, ?)", [namespace, key, value]);
            return true;
        }
        else 
            return false;
    }
    /**
     * @function remove
     * @description removes the item with the given key and namespace
     * @param {Object} key
     * @param {Object} namespace
     */
    function remove(key, namespace){
        if (isAvailable) {
            db.execute("DELETE FROM " + tableName +
            " WHERE namespace = ? AND key = ?", [namespace, key]);
            return true;
        }
        else {
            return false;
        }
    }
    
    return {
        clear: clear,
        get: get,
        getAll: getAll,
        isAvailable: isAvailable,
        put: put,
        remove: remove
    }
}();

/**
 * @function yvmobile.sql
 * @description
 *
 */
yvmobile.sql = function(){


    return {
        // Public members
        dbName: null,
        exec: function(sql, args){
            try {
                // get the Gears Database object
                this._initDb();
                
                // see if we need to open the db; if programmer
                // manually called yvmobile.sql.open() let them handle
                // it; otherwise we open and close automatically on
                
                // each SQL execution
                if (!this._dbOpen) {
                    this.open();
                    this._autoClose = true;
                }
                // determine our parameters
                /*var sql = null;
                 alert  ("params:"+ params);
                 // convert the params to an array
                 var args = yvmobile.utils.toArray(params);
                 alert("args:"+args);
                 sql = args.splice(0, 1)[0];
                 alert (sql);*/
                // 'args' now just has the SQL parameters
                
                // print out debug SQL output if the developer wants that
                
                /*if (debug) {
                 _printDebugSQL(sql, args);
                 }*/
                // execute the SQL and get the results
                
                //alert("db.execute:"+sql+"args:"+args);
                
                
                
                //var rs = this.db.execute(sql, args);
                
                var rs = this.db.execute(sql, args);
                
                // Gears ResultSet object's are ugly -- normalize
                // these into something JavaScript programmers know
                // how to work with, basically an array of 
                // JavaScript objects where each property name is
                // simply the field name for a column of data
                var results = [];
                
                while (rs.isValidRow()) {
                    var row = {};
                    
                    for (var i = 0; i < rs.fieldCount(); i++) {
                        var fieldName = rs.fieldName(i);
                        var fieldValue = rs.field(i);
                        row[fieldName] = fieldValue;
                        
                    }
                    results.push(row);
                    rs.next();
                }
                
                rs.close();
                
                if (this._autoClose) {
                    this.close();
                }
                
                return results;
            } 
            catch (exp) {
                exp = exp.message || exp;
                alert(exp);
                //console.debug("SQL Exception: " + exp);
                if (this._autoClose) {
                    try {
                        close();
                    } 
                    catch (e) {
                        /*console.debug("Error closing database: " 
                         + e.message||e);*/
                    }
                }
                
                throw exp;
            }
        },
        
        open: function(dbName){
            if (this._dbOpen && (!dbName || dbName == this.dbName)) {
                return;
            }
            
            if (!this.dbName) {
                this.dbName = "dot_store_" +
                window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
                ;;;yvmobile.logger.log("yvmobile.sql.open.dbName:" + this.dbName);
            }
            
            if (!dbName) {
                dbName = this.dbName;
            }
            
            try {
                this._initDb();
                this.db.open(dbName);
                this._dbOpen = true;
                
            } 
            catch (exp) {
                throw exp.message || exp;
            }
        },
        // Private members
        _initDb: function(){
            if (!this.db) {
                try {
                    this.db = google.gears.factory.create('beta.database');
                } 
                catch (exp) {
                    alert(exp);
                    //dojo.setObject("google.gears.denied", true);
                    //dojox.off.onFrameworkEvent("coreOperationFailed");
                    throw "Google Gears must be allowed to run";
                }
            }
        },
        
        close: function(dbName){
        
            // on Internet Explorer, Google Gears throws an exception
            // "Object not a collection", when we try to close the
            // database -- just don't close it on this platform
            // since we are running into a Gears bug; the Gears team
            // said it's ok to not close a database connection
            
            // Test for MSIE. Take it from http://www.javascriptkit.com/javatutors/navigator.shtml
            if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) {
                return;
            }
            
            if (!this._dbOpen && (!dbName || dbName == this.dbName)) {
                return;
            }
            
            if (!dbName) {
                dbName = this.dbName;
            }
            
            try {
                this.db.close(dbName);
                this._dbOpen = false;
            } 
            catch (exp) {
                throw exp.message || exp;
            }
        }
    }
}();

/**
 * @function yvmobile.ajax
 * @description This is a simple ajax function that let you POST and GET data.
 **/
yvmobile.ajax = function(){

    var timeout = 5;
    var xhr = null;
    
    // Try to create the XMLHttpRequest Object
    if (window.XMLHttpRequest) {
        try {
            xhr = new XMLHttpRequest();
        } 
        catch (e) {
            xhr = null;
        }
        // branch for IE/Windows ActiveX version
    }
    else {
        if (window.ActiveXObject) {
            try {
                xhr = new ActiveXObject("Msxml2.XMLHTTP");
            } 
            catch (e) {
                try {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } 
                catch (e) {
                    xhr = null;
                }
            }
        }
    }
    /**
     * Set the timeout in the AJAX request
     * @param {Object} t
     */
    function setRequestTimeout(t){
        timeout = t;
    }
    
    // PUBLIC FUNCTIONS
    /**
     * Make a AJAX request using POST or GET
     * @param {Object} url URL to call in the AJAX request
     * @param {Object} callback Called in case you get a response or a time out
     * @param {Object} method if it is a POST or GET request
     * @param {Object} parameters This are the parameters that are send to the server in case you make a POST
     */
    function request(url, callback, method, parameters){
        //;;;yvmobile.logger.debug('yvmobile.ajax.request');
        //OPEN
        xhr.open(method, url, true);
        
        if (xhr) {
            // In case the ajax call do not success we create a timer to do he callback
            timer = window.setTimeout(function(){
                ;;;yvmobile.logger.debug("timeout");
                try {
                    // Abort the connection
                    xhr.abort();
                } 
                catch (err) {
                    // Do the callback
                    ;;;yvmobile.logger.debug("Can Not make a connection with the server");
                }
                callback("", false);
            }, timeout * 1000);
            
            // If we receive response from the server
            xhr.onreadystatechange = function(){
                if (xhr && xhr.readyState == 4) {
                    if (xhr.status == 200 || window.location.href.indexOf("http") == -1) //if request was successful or running script locally
                    {
                        // If the connection success we clear the timer
                        window.clearTimeout(timer);
                        
                        //;;;yvmobile.logger.debug('xhr.onreadystatechange' + xhr.responseText);
                        
                        // Do the callback		
                        callback(xhr.responseText, true);
                        
                    }
                }
            }
        }
        // GET and POST request use differents HEADERS and parameters
        // If ajax request is a GET
        if (method == "GET") {
            // In GET request parameters must be null so we ensure this
            parameters == null;
            
            // HEADERS
            xhr.setRequestHeader("Content-Type", "text/plain");
            
        }
        
        // if the ajax request is a POST

        if (method == "POST") {
        	alert("asdfasdf"+parameters);
            // HEADERS
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhr.setRequestHeader("Content-length", parameters.length);
            xhr.setRequestHeader("Connection", "close");
        }
        
        //Send the request
        xhr.send(parameters);
        
    }
    return {
        request: request,
        setRequestTimeout: setRequestTimeout
    
    
    }
}();

/**
 * @function yvmobile.utils
 * @description just a hodge-podge of utility functions used by the other yvmobile libraries
 * the json module includes some regexs that look like unterminated strings,
 * so some minification tools might choke on it
 **/
yvmobile.utils = function(){
    return {
    
        createShortcutDesktop: function(name, resources, description){
            var desktop = google.gears.factory.create('beta.desktop');
            if (desktop) {
                desktop.createShortcut('Path Application', 'http://yaao.dyndns.org:8080', {
                    '128x128': 'http://yaao.dyndns.org:8080/icon128x128.png',
                    '48x48': 'http://yaao.dyndns.org:8080/icon48x48.png',
                    '32x32': 'http://yaao.dyndns.org:8080/icon32x32.png',
                    '16x16': 'http://yaao.dyndns.org:8080/icon16x16.png'
                }, 'An application at http://yaao.dyndns.org:8080');
            }
        },
        
        getLatiudeLongitude: function(){
        
        },
        /**
         * @function randomUUID
         * @description Create a UUID Number
         * @param {Object} len
         * Thanks to http://www.broofa.com/blog/?p=151
         */
        randomUUID: function(len){
            var uuid = [], me = arguments.callee, c = me.chars;
            if (!c) 
                c = me.chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
            if (len) {
                // Compact uuid form
                for (var i = 0; i < len; i++) 
                    uuid[i] = c[0 | Math.random() * 62];
            }
            else {
                var ri = 0, r;
                uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
                uuid[14] = '4';
                for (var i = 0; i < 36; i++) {
                    if (uuid[i]) 
                        continue;
                    r = 0 | Math.random() * 16;
                    // i==19: set the high bits of clock sequence as per rfc4122, sec. 4.1.5
                    uuid[i] = c[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf];
                }
            }
            return uuid.join('');
        },
        
        startsWith: function(input, prefix){
            return (input.substr(0, prefix.length) == prefix);
        },
        
        replaceNonAlphaNumericCharacters: function(input){
            var output = input.replace(/[^0-9A-Za-z_]/g, "_");
            return output;
        },
        
        sameDomain: function(url1, url2){
            url1 = url1.toLowerCase();
            url2 = url2.toLowerCase();
            
            // exact same url == match
            if (url1 == url2) 
                return true;
            // relative url == match
            if (!yvmobile.utils.startsWith(url1, "http://") && !yvmobile.utils.startsWith(url1, "https://")) 
                return true;
            // same domain == match
            var index1 = url1.indexOf("/", 8);
            var index2 = url2.indexOf("/", 8);
            if (index1 == -1) 
                index1 = url1.length - 1;
            if (index2 == -1) 
                index2 = url2.length - 1;
            var domain1 = url1.substring(0, index1);
            var domain2 = url2.substring(0, index2);
            if (domain1 == domain2) 
                return true;
            // all else == fail
            return false;
        },
        
        json: function(){
            // Test for modern browser (any except IE5).
            var JS13 = ('1'.replace(/1/, function(){
                return '';
            }) ==
            '');
            
            // CHARS array stores special strings for encodeString() function.
            var CHARS = {
                '\b': '\\b',
                '\t': '\\t',
                '\n': '\\n',
                '\f': '\\f',
                '\r': '\\r',
                '\\': '\\\\',
                '"': '\\"'
            };
            
            for (var i = 0; i < 32; i++) {
                var c = String.fromCharCode(i);
                if (!CHARS[c]) 
                    CHARS[c] = ((i < 16) ? '\\u000' : '\\u00') + i.toString(16);
            };
            
            function encodeString(str){
                if (!/[\x00-\x1f\\"]/.test(str)) {
                    return str;
                }
                else 
                    if (JS13) {
                        return str.replace(/([\x00-\x1f\\"])/g, function($0, $1){
                            return CHARS[$1];
                        });
                    }
                    else {
                        var out = new Array(str.length);
                        for (var i = 0; i < str.length; i++) {
                            var c = str.charAt(i);
                            out[i] = CHARS[c] || c;
                        }
                        return out.join('');
                    }
            };
            
            return {
                /**
                 * yvmobile.utils.json.stringify(obj)
                 * @description Convert a JSON objet to string
                 * @param {Object} arg is the object to convert
                 * @returns a json-formatted string
                 */
                stringify: function(arg){
                    switch (typeof arg) {
                        case 'string':
                            return '"' + encodeString(arg) + '"'; // break command is redundant ere and below.
                        case 'number':
                            return String(arg);
                        case 'object':
                            if (arg) {
                                var out = [];
                                if (arg instanceof Array) {
                                    for (var i = 0; i < arg.length; i++) {
                                        var json = this.stringify(arg[i]);
                                        if (json != null) 
                                            out[out.length] = json;
                                    }
                                    return '[' + out.join(',') + ']';
                                }
                                else {
                                    for (var p in arg) {
                                        var json = this.stringify(arg[p]);
                                        if (json != null) 
                                            out[out.length] = '"' + encodeString(p) + '":' + json;
                                    }
                                    return '{' + out.join(',') + '}';
                                }
                            }
                            return 'null'; // if execution reaches here, arg is null.
                        case 'boolean':
                            return String(arg);
                        // cases function & undefined return null implicitly.
                    }
                },
                /**
                 * @function yvmobile.utils.json.parse(json)
                 * @description where json is the json-formatted string to parse.
                 * @returns a javascript object
                 * @param {Object} text
                 */
                parse: function(text){
                    try {
                        return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(text.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + text + ')');
                    } 
                    catch (e) {
                        ;;;yvmobile.logger.error(e.description);
                        return false;
                    }
                }
            }
        }()
    }
}();

/**
 * @function yvmobile.logger
 * @description Create a console and print log information
 * NOTE: CSS styles padding and overflow are NOT supported
 * by Internet Explorer Mobile 6.1 they are used for browser
 * that support it
 */
yvmobile.logger = function(){
    var _console = null;
    var _consoleName = "yvmobile-console";
    var _consoleBodyName = "body-console";
    var _consoleTitleName = "title-console";
    var _debug = false;
    var _logger;
    var _bodyLogger;
    /**
     * @function: init
     * @description: Initialize the logger console window
     * @param {Object} debug Tell the if should be initialice
     */
    function init(debug){
        _debug = debug;
        //TODO: I am trying to add dinamically the logger window but Internet Explorer Mobile has a weird behavior
        //document.body.innerHTML += "<div class='console' id='yvmobile-console'></div>";
        //document.getElementsByTagName("body").item(0).innerHTML += "<div class='console' id='yvmobile-console'></div>";
        // Verify if the console exist
        if (debug == false) {
            return true;
        }
        
        _logger = document.createElement('div');
        //alert(_logger);
        //_logger.setAttribute('id',_consoleName);
        document.body.appendChild(_logger);
        
        // Windows Mobile 6.1 Explode
        // Try to put the Logger Console at the top of the body
        //document.body.insertBefore(_logger, document.body.firstChild);
        
        //if (document.getElementById(_consoleName)) {
        _logger.innerHTML += ("<div id='" + _consoleTitleName + "'><a href='#' onclick='yvmobile.logger.clearConsole();'>Clear</a><a href='#' onclick='yvmobile.logger.closeConsole();'>    Close</a></div>");
        _logger.innerHTML += ("<div id='" + _consoleBodyName + "'></div>");
        
        _bodyLogger = document.getElementById(_consoleBodyName);
        /*_bodyLogger = document.createElement('div');
         _bodyLogger.setAttribute('id',_consoleBodyName);
         _logger.appendChild(_bodyLogger);*/
        //= document.getElementById('_consoleBodyName');
        return true;
        /*}
         else {
         alert("You must put this  <div class='console' id='yvmobile-console'></div> in the page you want to put the logger console");
         _debug = false;
         return false;
         }*/
    }
    /**
     * log
     * @param {Object} obj
     */
    function log(msg){
        if (_debug == false) 
            return;
        append(msg, 'log');
        
    }
    /**
     * debug
     * @param {Object} obj
     */
    function debug(msg){
        if (_debug == false) 
            return;
        append(msg, 'debug');
        
    }
    /**
     * info
     * @description It can be use to print information usefull for the final user
     * @param {Object} obj
     */
    function info(msg){
        if (_debug == false) 
            return;
        append('Info:' + msg, 'info');
        
    }
    /**
     * warn
     * @description
     * @param {Object} obj
     */
    function warn(msg){
        if (_debug == false) 
            return;
        append('Warning:' + msg, 'warn');
        
    }
    /**
     * error
     * @description It can be use to print on error function
     * @param {Object} obj
     */
    function error(msg){
        if (_debug == false) 
            return;
        append('Error:' + msg, 'error');
    }
    /**
     * @function append
     * @param {Object} msg message to Log
     * @param {Object} className
     */
    function append(msg, className){
        if (_bodyLogger) {
            var tm = new Date();
            var hours = tm.getUTCHours();
            var minutes = tm.getUTCMinutes();
            var seconds = tm.getUTCSeconds();
            _bodyLogger.innerHTML += ("<div class='" + className + " border'>" + hours + ":" + minutes + ":" + seconds + ":" + msg + "</div>");
        }
    }
    
    /**
     * inpector
     * @description Print a object's Attributes
     * @param {Object} el Object which you want to know its Attributes
     */
    function inspector(el){
        var str = "";
        for (var i in el) {
            str += i + ": " + el.getAttribute(i) + "\n";
        }
        log(str);
    }
    /**
     * @function closeConsole
     * @description Close the debug console
     *
     */
    function closeConsole(){
        if (_bodyLogger) {
            _bodyLogger.innerHTML = "";
        }
        if (_bodyLogger) {
            _bodyLogger.style.visibility = "hidden";
            _bodyLogger.style.height = "0px";
        }
        // release
        _logger = null;
        _bodyLogger = null;
        
        // Not debug Anymore
        //_debug = false;
    }
    /**
     * @function clearConsole
     * @description Clear the debug console info
     */
    function clearConsole(){
        if (_bodyLogger) {
            _bodyLogger.innerHTML = "";
            _bodyLogger.style.height = "80px";
        }
    }
    return {
		// PUBLIC FUNCTIONS
        init: init,
        log: log,
        debug: debug,
        info: info,
        warn: warn,
        error: error,
        inspector: inspector,
        closeConsole: closeConsole,
        clearConsole: clearConsole
    }
}();

